/*
  author Sylvain Bertrand <sylvain.bertrand@gmail.com>
  Protected by linux GNU GPLv2
  Copyright 2012-2014
*/
#include <linux/pci.h>
#include <linux/cdev.h>
#include <asm/unaligned.h>

#include <alga/rng_mng.h>
#include <uapi/alga/pixel_fmts.h>
#include <alga/timing.h>
#include <alga/amd/atombios/atb.h>
#include <uapi/alga/amd/dce6/dce6.h>
#include <alga/amd/atombios/vm.h>
#include <alga/amd/atombios/cm.h>
#include <alga/amd/atombios/pp.h>
#include <alga/amd/atombios/vram_info.h>

#include "../mc.h"
#include "../rlc.h"
#include "../ih.h"
#include "../fence.h"
#include "../ring.h"
#include "../dmas.h"
#include "../ba.h"
#include "../cps.h"
#include "../gpu.h"
#include "../drv.h"

#include "../regs.h"

#include "../smc_tbls.h"

#include "ctx.h"
#include "private.h"
#include "smc_volt.h"

#ifdef CONFIG_ALGA_AMD_SI_DYN_PM_LOG
#define L(fmt,...) printk(KERN_INFO fmt "\n", ##__VA_ARGS__)
void smc_volt_dump(struct smc_volt *tbl)
{
	u16 tmp;

	tmp = get_unaligned_be16(&tbl->val);
	L("val=0x%04x",tmp);

	L("step_idx=0x%02x",tbl->step_idx);
	L("phase_settings=0x%02x",tbl->phase_settings);
}
#endif

static u8 volt_step_idx(struct atb_volt_tbl *tbl, u16 val_mv)
{
	u8 step;

	for (step = 0; step < tbl->entries_n; ++step) {
		/* locate a step powerfull enough */
		if (tbl->entries[step].val_mv >= val_mv)
			break;
	}

#if 0
	if (step == tbl->entries_n)
		return -SI_ERR;
#endif
	return step;
}

long smc_volt_vddc_set_from_atb_id(struct ctx *ctx, struct smc_volt *vddc,
								u32 vddc_id)
{
	u16 vddc_mv; /* vddc in mV units, not lkge index */
	long r;

	if (IS_VDDC_LKGE_IDX(vddc_id)) {
		struct dev_drv_data *dd;

		dd = pci_get_drvdata(ctx->dev);

		r = atb_volt_get(dd->atb, ATB_VOLT_TYPE_VDDC, vddc_id,
								&vddc_mv);	
		if (r == -ATB_ERR) {
			dev_err(&ctx->dev->dev, "dyn_pm:unable to find vddc mV value for leakage index 0x%04x\n",
								vddc_id);
			return -SI_ERR;
		}
			
	} else
		vddc_mv = vddc_id;

	vddc->step_idx = volt_step_idx(&ctx->atb_vddc_tbl, vddc_mv);
	put_unaligned_be16(ctx->atb_vddc_tbl.entries[vddc->step_idx].val_mv,
								&vddc->val);
	return 0;
}

static void mv_set_from_atb(struct ctx *ctx, struct smc_volt *volt,
				struct atb_volt_tbl *atb_volt_tbl, u32 volt_mv)
{
	volt->step_idx = volt_step_idx(atb_volt_tbl, volt_mv);
	put_unaligned_be16(atb_volt_tbl->entries[volt->step_idx].val_mv,
								&volt->val);
}

void smc_volt_vddc_set_from_atb_mv(struct ctx *ctx, struct smc_volt *vddc,
								u32 vddc_mv)
{
	mv_set_from_atb(ctx, vddc, &ctx->atb_vddc_tbl, vddc_mv);
}

void smc_volt_vddci_set_from_atb_mv(struct ctx *ctx, struct smc_volt *vddci,
								u32 vddci_mv)
{
	mv_set_from_atb(ctx, vddci, &ctx->atb_vddci_tbl, vddci_mv);
}

/* only used by the initial state */
void smc_volt_mvdd_set_from_atb_mv(struct ctx *ctx, struct smc_volt *mvdd,
								u32 mvdd_mv)
{
	mv_set_from_atb(ctx, mvdd, &ctx->atb_mvddc_tbl, mvdd_mv);
}

#define MVDD_SPLIT_FREQ 30000 /* 10 kHz units */
void smc_volt_mvdd_set_from_atb_mem_clk(struct ctx *ctx, struct smc_volt *mvdd,
								u32 mem_clk)
{
	u8 idx;

	if (mem_clk < MVDD_SPLIT_FREQ)
		idx = 0;
	else
		/* use the highest volt in atb tbl */
		idx = ctx->atb_mvddc_tbl.entries_n - 1;

	mvdd->step_idx = idx;
	put_unaligned_be32(ctx->atb_mvddc_tbl.entries[idx].val_mv, &mvdd->val);
}

static void std_vddc_from_old_cac_lkge_tbl(struct ctx *ctx,
			struct smc_volt *std_vddc, struct smc_volt *vddc)
{
	LOG("computing std_vddc from old cac table");

	if (vddc->step_idx < ctx->atb_cac_lkge_tbl.entries_n) {
		put_unaligned_be16(
			ctx->atb_cac_lkge_tbl.entries[vddc->step_idx].vddc_mv,
								&std_vddc->val);
		std_vddc->step_idx = vddc->step_idx;
	} else {
		std_vddc->val = vddc->val;
		std_vddc->step_idx = vddc->step_idx;
	}
}

static void std_vddc_from_new_cac_lkge_entry(struct ctx *ctx,
		struct smc_volt *std_vddc, u8 vddc_step_idx, u8 dep_tbl_idx)
{
	if (dep_tbl_idx < ctx->atb_cac_lkge_tbl.entries_n) {
		put_unaligned_be16(
			ctx->atb_cac_lkge_tbl.entries[dep_tbl_idx].vddc_mv,
			&std_vddc->val);

		LOG("std_vddc from corresponding cac leakage entry for found vddc on engine clock dependency entry");
	} else {
		u8 last;

		last = ctx->atb_cac_lkge_tbl.entries_n - 1;
		put_unaligned_be16(ctx->atb_cac_lkge_tbl.entries[last].vddc_mv,
								&std_vddc->val);

		LOG("std_vddc from last cac leakage entry");
	}
	std_vddc->step_idx = vddc_step_idx;
	LOG("std_vddc=%umV step_idx=%u", get_unaligned_be16(&std_vddc->val),
							std_vddc->step_idx);
}

static void std_vddc_from_new_cac_lkge_tbl(struct ctx *ctx,
			struct smc_volt *std_vddc, struct smc_volt *vddc)
{
	u8 dep_tbl_idx;
	long r;

	LOG("computing std_vddc from new cac table");

	if (!ctx->atb_vddc_dep_on_sclk_tbl.entries_n) {
		LOG("empty vddc on engine clk dependency table");
		goto exit_set_std_vddc_to_vddc;
	}

	/* will try to find an exact match */
	for (dep_tbl_idx = 0;
			dep_tbl_idx < ctx->atb_vddc_dep_on_sclk_tbl.entries_n;
								++dep_tbl_idx) {
		u32 dep_vddc_id;
		u16 dep_vddc_mv;

		dep_vddc_id = ctx->atb_vddc_dep_on_sclk_tbl.entries[dep_tbl_idx]
								.volt_id;

		if (IS_VDDC_LKGE_IDX(dep_vddc_id)) {
			struct dev_drv_data *dd;
	
			dd = pci_get_drvdata(ctx->dev);
			
			r = atb_volt_get(dd->atb, ATB_VOLT_TYPE_VDDC,
						dep_vddc_id, &dep_vddc_mv);
			if (r == -ATB_ERR) {
				dev_err(&ctx->dev->dev, "dyn_pm:unable to convert a leakage index in vddc on engine clock dependency table\n");
				continue; /* we insist */
			}
		} else
			dep_vddc_mv = dep_vddc_id;

		/* exact match */
		if (dep_vddc_mv == get_unaligned_be16(&vddc->val)) {
			LOG("found exact vddc on engine clock dependency entry");
			std_vddc_from_new_cac_lkge_entry(ctx, std_vddc,
						vddc->step_idx, dep_tbl_idx);
			goto exit_found;
		}
	}

	/*
	 * Will try to find an approximate match: The tbl is from low volt to
	 * high volt, then try to find a volt step which will provide a bit
	 * more volt than the targetted vddc.
	 */
	for (dep_tbl_idx = 0;
			dep_tbl_idx < ctx->atb_vddc_dep_on_sclk_tbl.entries_n;
								++dep_tbl_idx) {
		u16 dep_vddc_id;
		u16 dep_vddc_mv;

		dep_vddc_id = ctx->atb_vddc_dep_on_sclk_tbl.entries[dep_tbl_idx]
								.volt_id;

		if (IS_VDDC_LKGE_IDX(dep_vddc_id)) {
			struct dev_drv_data *dd;
	
			dd = pci_get_drvdata(ctx->dev);
			
			r = atb_volt_get(dd->atb, ATB_VOLT_TYPE_VDDC,
						dep_vddc_id, &dep_vddc_mv);
			if (r == -ATB_ERR) {
				dev_err(&ctx->dev->dev, "dyn_pm:unable to convert a leakage index in vddc on engine clock dependency table\n");
				continue; /* we insist */
			}
		} else
			dep_vddc_mv = dep_vddc_id;

		/* approximate match */
		if (dep_vddc_mv <= get_unaligned_be16(&vddc->val)) {
			LOG("found approximate vddc on engine clock dependency entry");
			std_vddc_from_new_cac_lkge_entry(ctx, std_vddc,
						vddc->step_idx, dep_tbl_idx);
			break;
		}
	}

exit_found:
	return;

exit_set_std_vddc_to_vddc:
	std_vddc->val = vddc->val;
	std_vddc->step_idx = vddc->step_idx;
	return;
}

/* will put vddc in std_vddc if it encounters errors */
void smc_volt_std_vddc_compute(struct ctx *ctx, struct smc_volt *std_vddc,
							struct smc_volt *vddc)
{
	LOG("computing std_vddc");

	if (!ctx->atb_cac_lkge_tbl.entries_n) {
		LOG("empty cac leakage table");
		return;
	}

	if (ctx->platform_caps & ATB_PP_PLATFORM_CAPS_NEW_CAC_VOLT)
		std_vddc_from_new_cac_lkge_tbl(ctx, std_vddc, vddc);
	else
		std_vddc_from_old_cac_lkge_tbl(ctx, std_vddc, vddc);
}
